package com.googlecode.chelonia;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.imageio.ImageIO;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

public class MapImage {
	private BufferedImage image;
	private int width;
	private int height;
	private float factor = 1;
	private static Map<String, String> codes;
	private static Pattern pattern = Pattern.compile("([MmLl])\\s+(\\d+(\\.\\d+)),\\s*(\\d+(\\.\\d+))");

	static {
		codes = createCodes("com/googlecode/chelonia/africa.svg");
	}

	public MapImage(int width, int height) {
		this.width = width;
		this.height = height;
		factor = 0.001875f * width;
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	public Graphics2D getGraphics() {
		Graphics2D g = getImage().createGraphics();

		RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHints(hints);
		return g;
	}

	private BufferedImage getImage() {
		// create image before first use, it saves some memory
		if(image == null){
			image =  new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		}
		return image;
	}

	public void setBackground(Color color) {
		Graphics2D g = getGraphics();
		g.setColor(color);
		g.fill(new Rectangle(0, 0, getWidth(), getHeight()));
		g.finalize();
	}

	public void paintBorders(Color col, CountrySet countries, float lineWidth) {
		Stroke stroke = new BasicStroke(lineWidth);

		Graphics2D g = getGraphics();
		g.setColor(col);

		for (String key : countries) {
			Matcher matcher = pattern.matcher(codes.get(key));
			GeneralPath path = new GeneralPath();

			while (matcher.find()) {
				String type = matcher.group(1).toLowerCase();
				if ("m".equals(type)) {
					path.moveTo(Float.parseFloat(matcher.group(2)), Float.parseFloat(matcher.group(4)));
				} else if ("l".equals(type)) {
					path.lineTo(Float.parseFloat(matcher.group(2)), Float.parseFloat(matcher.group(4)));
				}
			}
			path.transform(AffineTransform.getScaleInstance(factor, factor));
			g.fill(stroke.createStrokedShape(path));
		}
		g.finalize();
	}

	public void paintCountries(Color col, CountrySet countries) {
		Graphics2D g = getGraphics();
		g.setColor(col);

		for (String key : countries) {
			Matcher matcher = pattern.matcher(codes.get(key));
			GeneralPath path = new GeneralPath();

			while (matcher.find()) {
				String type = matcher.group(1).toLowerCase();
				if ("m".equals(type)) {
					path.moveTo(Float.parseFloat(matcher.group(2)), Float.parseFloat(matcher.group(4)));
				} else if ("l".equals(type)) {
					path.lineTo(Float.parseFloat(matcher.group(2)), Float.parseFloat(matcher.group(4)));
				}
			}
			path.transform(AffineTransform.getScaleInstance(factor, factor));
			g.fill(path);
		}
		g.finalize();
	}

	public void toImage(String extension, OutputStream out) {
		try {
			ImageIO.write(getImage(), extension, out);
			out.flush();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}

	public static Map<String, String> createCodes(String filename) {
		Map<String, String> codes = new HashMap<String, String>();

		InputStream in = MapImage.class.getClassLoader().getResourceAsStream(filename);

		SAXReader reader = new SAXReader();
		InputStreamReader isr;
		try {
			isr = new InputStreamReader(in, "UTF-8");
			Document document = reader.read(isr);

			List l = document.selectNodes("/svg/g/path");

			for (Object o : l) {
				Node n = (Node) o;

				String name = n.selectSingleNode("@id").getStringValue();
				String code = n.selectSingleNode("@d").getStringValue();

				codes.put(name, code);
			}

		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (DocumentException e) {
			e.printStackTrace();
		}

		return codes;
	}

}
